/*
 * Copyright 2004-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package susano.common.util;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import net.arnx.jsonic.JSON;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

/**
 * エンティティを扱うためのユーティリティ
 *
 * @author tanaka.akira.2006
 * @version $Id: EntityUtil.java 685 2009-11-04 07:36:33Z tanaka.akira.2006 $
 *
 */
public final class EntityUtil {

	protected static final Logger logger = Logger.getLogger(EntityUtil.class
			.getName());

	public static String toJSON(final Entity entity) {
		return JSON.encode(entity);
	}

	public static String toJSON(final List<Entity> entities) {
		return JSON.encode(entities);
	}

	public static Entity toEntity(final String jsonStr) {
		return JSON.decode(jsonStr, Entity.class);
	}

	/**
	 * エンティティからオブジェクトへプロパティをコピーします。
	 *
	 * @param src
	 * @param dest
	 */
	public static <T> void entityToModel(final Entity src, T dest) {
		if (src == null) {
			throw new NullPointerException("src is null.");
		}
		if (dest == null) {
			throw new NullPointerException("dest is null.");
		}
		copyEntityToObject(src, dest);
	}

	/**
	 * エンティティからモデルへデータコピーします。
	 *
	 * @param src
	 *            エンティティ
	 * @param dest
	 *            モデル
	 */
	private static <T> void copyEntityToObject(final Entity src, T dest) {
		Set<String> fieldnames = src.getProperties().keySet();
		for (String fieldname : fieldnames) {
			try {
				Field f = dest.getClass().getField(fieldname);
				f.set(dest, src.getProperty(fieldname));
			} catch (SecurityException e) {
				throw new RuntimeException(e);
			} catch (NoSuchFieldException e) {
				throw new RuntimeException(e);
			} catch (IllegalArgumentException e) {
				throw new RuntimeException(e);
			} catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * {@link Map}なモデルからエンティティを生成して返します。
	 * <p>
	 * parentKeyStr==nullの場合は自分自身をROOTエンティティとして生成。
	 * </p>
	 *
	 * @param model
	 *            {@link Map}なモデル
	 * @param parentKeyStr
	 *            親エンティティのキー。<br>
	 * @param kind
	 * @return エンティティ
	 */
	public static Entity createEntity(final Map<String, Object> model,
			final String parentKeyStr, final String kind) {
		if (model == null)
			throw new NullPointerException("model is null.");

		if (StringUtil.isEmpty(kind))
			throw new IllegalArgumentException("kind is empty.");

		Entity entity = null;
		if (parentKeyStr != null) {
			// keyStrが存在する場合は子エンティティとして生成
			Key parentKey = KeyFactory.stringToKey(parentKeyStr);
			entity = new Entity(kind, parentKey);
		} else {
			// keyStrがnullの場合は親エンティティとして生成
			entity = new Entity(kind);
		}
		setMapPropsToEntity(model, entity);

		return entity;
	}

	/**
	 * モデルから親エンティティを生成します。
	 * <p>
	 * Entityのkindは <code>model.getClass().getSimpleName()</code>を利用します。
	 * </p>
	 *
	 * @param model
	 *            {@link Map}なモデル
	 * @param kind
	 * @return エンティティ
	 */
	public static Entity createRootEntity(final Map<String, Object> model,
			final String kind) {
		if (StringUtil.isEmpty(kind))
			throw new NullPointerException("kind is empty.");
		return createEntity(model, null, kind);
	}

	/**
	 * エンティティリストからBeanMapリストを作成します。
	 *
	 * @param entities
	 * @return {@link BeanMap} のリスト
	 */
	public static List<BeanMap> createBeanMaps(final List<Entity> entities) {
		List<BeanMap> bms = new ArrayList<BeanMap>(entities.size());
		for (int i = 0; i < entities.size(); i++) {
			Entity entity = entities.get(i);
			if (entity == null) {
				continue;
			}
			BeanMap bm = new BeanMap(entity.getProperties());

			// set key
			if (!entity.getKey().isComplete()) {
				logger.warning("key is not complete. entities(" + i
						+ "), kind='" + entity.getKind() + "'");
				continue;
			}
			String keyStr = KeyFactory.keyToString(entity.getKey());
			bm.put(Entity.KEY_RESERVED_PROPERTY, keyStr);

			bms.add(bm);
		}
		return bms;
	}

	private static void setMapPropsToEntity(final Map<String, Object> map,
			Entity entity) {
		Set<String> keyset = map.keySet();
		for (String key : keyset) {
			if (key.equals(Entity.KEY_RESERVED_PROPERTY)) {
				continue;
			}
			Object val = map.get(key);
			entity.setProperty(key, val);
		}
	}

}
